#include <stdio.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavcodec/version.h>
#include <libavdevice/avdevice.h>
#include <libavdevice/version.h>
#include <libavformat/version.h>
#include <libavutil/avutil.h>
#include <libavutil/version.h>
#include "device.h"
#include "query_hw_codecInfo.h"

#include "utils.h"




static int loacal_abs(unsigned int value1, unsigned int value2)
{
    if (value1 > value2)
    {
        return value1 - value2;
    }
    else
    {
        return value2 - value1;
    }
}

compare_result_t compare_yuv420p_nv12(int width, int height, unsigned char *yuvdata, unsigned char *nv12data)
{
    compare_result_t cmp_result={};
    unsigned char *hw_buffer = nv12data;
    unsigned char *sw_buffer = yuvdata;
    int y_compare_same = 0;
    int y_compare_diff = 0;
    int u_compare_same = 0;
    int u_compare_diff = 0;
    int v_compare_same = 0;
    int v_compare_diff = 0;
    int x = 0;
    int y = 0;
    int z = 0;
    // compare y
    for (x = 0; x < height; x++)
    {
        for (y = 0; y < width; y++)
        {
            int hwvalue = *(hw_buffer + x * width + y);
            int swvalue = *(sw_buffer + x * width + y);
            if (loacal_abs(hwvalue, swvalue) > DEFAULT_DELTA_VALUE)
            {
                y_compare_diff++;
                printf("compare y not match soft dec, position is (%d, %d), value(sw-%d, hw-%d).\n", x, y, swvalue, hwvalue);
                break;
            }
            else
                y_compare_same++;
        }
    }

    // compare uv
    unsigned char *sw_u = sw_buffer + width * height;
    unsigned char *sw_v = sw_u + (width * height) / 4;
    unsigned char *hw_uv = hw_buffer + width * height;
    for (z = 0; z < (width * height) / 4; z++)
    {
        int swu = sw_u[z];
        int hwu = hw_uv[2 * z];
        int swv = sw_v[z];
        int hwv = hw_uv[2 * z + 1];
        if (loacal_abs(hwu, swu) > DEFAULT_DELTA_VALUE) {
          u_compare_diff++;
          printf(
              "compare uv is match soft dec, position(z-%d), u(sw-%d, hw-%d), "
              "v(sw-%d, hw-%d).\n",
              z, swu, hwu, swv, hwv);
          break;
        } else {
          u_compare_same++;
        }
        if (loacal_abs(hwv, swv) > DEFAULT_DELTA_VALUE) {
          v_compare_diff++;
          printf(
              "compare uv is match soft dec, position(z-%d), u(sw-%d, hw-%d), "
              "v(sw-%d, hw-%d).\n",
              z, swu, hwu, swv, hwv);
          break;
        } else {
          v_compare_same++;
        }
    }
    cmp_result.width=width;
    cmp_result.height=height;
    cmp_result.y_compare_rate = 1.0 * y_compare_same / (width * height);
    cmp_result.u_compare_rate = 1.0 * u_compare_same / (width * height / 4);
    cmp_result.v_compare_rate = 1.0 * v_compare_same / (width * height / 4);
    cmp_result.total_compare_rate =
        (cmp_result.y_compare_rate * 4 + cmp_result.u_compare_rate +
         cmp_result.v_compare_rate) /
        6;
    return cmp_result;
}
static float compare_image_data(char* src,char* dst,int width,int height)
{
    int buffer_size=width*height*1.5;
    unsigned char* src_buffer=(unsigned char*)malloc(buffer_size);
    unsigned char* dst_buffer=(unsigned char*)malloc(buffer_size);
    FILE *fyuv = fopen(src, "rb");
    if (!fyuv)
    {
        fprintf(stderr, "Could not open %s\n", src);
    }
    else
    {
        fread(src_buffer, 1, buffer_size, fyuv);
        fclose(fyuv);
    }
    FILE *fnv12 = fopen(dst, "rb");
    if (!fnv12)
    {
        fprintf(stderr, "Could not open %s\n", dst);
    }
    else
    {
        fread(dst_buffer, 1, buffer_size, fnv12);
        fclose(fnv12);
    }
    compare_result_t result=compare_yuv420p_nv12(width, height, src_buffer, dst_buffer);
    if(src_buffer)free(src_buffer);
    if(dst_buffer)free(dst_buffer);
    return result.total_compare_rate;
}
float compare_dir_image(char* src_path,char* dst_path,int width,int height)
{
	DIR *dp;
	struct dirent *entry;
	struct stat statbuf;
	int pass_count=0;
	int fail_count=0;
	int test_count=0;
	dp = opendir(src_path);
	if (NULL == dp)
	{
		printf("opendir %s failed\n", src_path);
		return 0.0;
	}
	while((entry = readdir(dp)) != NULL)
	{
      char file[256] = {0};
      sprintf(file, "%s/%s", src_path, entry->d_name);
      lstat(file, &statbuf);
      if (S_ISDIR(statbuf.st_mode)) {
        if ((strcmp(entry->d_name, "..") == 0) ||
            (strcmp(entry->d_name, ".") == 0)) {
          continue;
        }
      } else if (S_ISREG(statbuf.st_mode)) {
        char src_image[256] = {};
        char dst_image[256] = {};
        sprintf(src_image, "%s/%s", src_path, entry->d_name);
        sprintf(dst_image, "%s/%s", dst_path, entry->d_name);
        if (access(dst_image, F_OK)) {
          fail_count++;
          continue;
        }
        float sim = compare_image_data(src_image, dst_image, width, height);
        printf("compare image %s in sw dir and hw dir,similarity is %5.2f%%\n", entry->d_name, sim*100);
#define SIMLARITY_GATE_VALUE 0.8
        if (sim >= SIMLARITY_GATE_VALUE)
          pass_count++;
        else
          fail_count++;
        test_count++;
      }
  }
  closedir(dp);
  return test_count > 0 ? (1.0 * pass_count / test_count) : 0.0;
}


static int test_vdpau_driver(const char* driver_name)
{
  int ret = test_vdpau();
  if (ret) {
    printf("default vdpau backend driver is not supported \n");
    setenv("VDPAU_DRIVER", driver_name, 1);
    if (test_vdpau()) {
      unsetenv("VDPAU_DRIVER");
      printf("vdpau backend driver %s is not supported \n", driver_name);
      return -1;
    } else {
      printf("vdpau backend driver %s is supported \n", driver_name);
    }
  }
  return 0;
}

static int test_vaapi_driver(const char* driver_name)
{
  int ret = test_vaapi();
  if (ret) {
    printf("default va backend driver is not supported \n");
    setenv("LIBVA_DRIVER_NAME", driver_name, 1);
    if (test_vaapi()) {
      unsetenv("LIBVA_DRIVER_NAME");
      printf("va backend driver %s is not supported \n", driver_name);
      return -1;
    } else {
      printf("va backend driver %s is supported \n", driver_name);
    }
  }
  return 0;
}
static int setup_env(int vendor_id,char* hw_type)
{
  int is_vaapi = 0;
  int is_vdpau = 0;

  int index = get_cardinfoindex_byid(vendor_id);

  setenv("LIBVA_MESSAGING_LEVEL", "1", 1);
  if (!strncasecmp(hw_type, "vaapi",5))
  {
      printf("setup_env vaapi, index(%d) ######################, %s \n", index, card_config_info[index].vaapi_path);
      if (index < 0)
          setenv("LIBVA_DRIVERS_PATH", "/usr/lib/x86_64-linux-gnu/dri", 1);
      else
          setenv("LIBVA_DRIVERS_PATH", card_config_info[index].vaapi_path, 1);
      is_vaapi = 1;
  }
  if (!strncasecmp(hw_type, "vdpau",5))
  {
      printf("setup_env vdpau, index(%d) ######################, %s \n", index, card_config_info[index].vdpau_path);
      if (index < 0)
          setenv("VDPAU_DRIVER_PATH", "/usr/lib/x86_64-linux-gnu/vdpau", 1);
      else
          setenv("LIBVA_DRIVERS_PATH", card_config_info[index].vdpau_path, 1);
  is_vdpau = 1;
  }
#if 1
  switch (vendor_id) {
    case 0x1002:
    {
      const char* amd_driver[]={"radeonsi"};
      int driver_cnt=sizeof(amd_driver)/sizeof(amd_driver[0]);
      for(int i=0;i<driver_cnt&&is_vaapi;i++)
      {
        if (!test_vaapi_driver(amd_driver[i]))
            break;
        printf("vaapi is not supported in %s using driver %s \n", get_vendor_name(vendor_id),amd_driver[i]);
      }
      for(int i=0;i<driver_cnt&&is_vdpau;i++)
      {
        if (!test_vdpau_driver(amd_driver[i]))
            break;
        printf("vdpau is not supported in %s using driver %s \n", get_vendor_name(vendor_id),amd_driver[i]);
      }
    }break;
    case 0x8086:
    {
      const char* intel_driver[]={"i965","iHD","va_gl"};
      int driver_cnt = sizeof(intel_driver) / sizeof(intel_driver[0]);
      for (int i = 0; i < driver_cnt && is_vaapi; i++)
      {
        if (!test_vaapi_driver(intel_driver[i]))
            break;
        printf("vaapi is not supported in %s using driver %s \n", get_vendor_name(vendor_id),intel_driver[i]);
      }
      for(int i=0;i<driver_cnt && is_vdpau;i++)
      {
        if (!test_vdpau_driver(intel_driver[i]))
            break;
        printf("vdpau is not supported in %s using driver %s \n", get_vendor_name(vendor_id),intel_driver[i]);
      }
    }break;
    case 0x6766:
    {
      const char* glenfly_driver[]={"arise"};
      int driver_cnt=sizeof(glenfly_driver)/sizeof(glenfly_driver[0]);
      for(int i=0;i<driver_cnt&&is_vaapi;i++)
      {
        if (!test_vaapi_driver(glenfly_driver[i]))
            break;
        printf("vaapi is not supported in %s using driver %s \n", get_vendor_name(vendor_id),glenfly_driver[i]);
      }
      for(int i=0;i<driver_cnt&&is_vdpau;i++)
      {
        if (!test_vdpau_driver(glenfly_driver[i]))
            break;
        printf("vdpau is not supported in %s using driver %s \n", get_vendor_name(vendor_id),glenfly_driver[i]);
      }
    }break;
    case 0x10DE:
    {
      const char* nvidia_driver[]={"nouveau","nvidia"};
      int driver_cnt=sizeof(nvidia_driver)/sizeof(nvidia_driver[0]);
      for(int i=0;i<driver_cnt&&is_vaapi;i++)
      {
        if (!test_vaapi_driver(nvidia_driver[i]))
            break;
        printf("vaapi is not supported in %s using driver %s \n", get_vendor_name(vendor_id),nvidia_driver[i]);
      }
      for(int i=0;i<driver_cnt&&is_vdpau;i++)
      {
        if (!test_vdpau_driver(nvidia_driver[i]))
            break;
        printf("vdpau is not supported in %s using driver %s \n", get_vendor_name(vendor_id),nvidia_driver[i]);
      }
    }break;
    default:
        break;
    }
#endif
    return 0;
}
int prepare_hwdec(char* hw_type)
{

    //if has discreted card,choose it,
    //if not chose first card to set up env
    device_info_t device_info={};
    get_device_info(&device_info);
    printf("card count             : %d\n", device_info.card_cnt);
    for(int i=0;i<device_info.card_cnt;i++)
    {
      card_info_t* card_info = &device_info.card_info[i];
      printf("card type              : %s\n", card_info->card_type==DISCRETE_CARD_TYPE?"Discrete Card":"Intergrated Cards");
      printf("device id              : 0x%x\n", card_info->device_id);
      printf("vendor id              : 0x%x(%s)\n", card_info->vendor_id,
      get_vendor_name(card_info->vendor_id));
    }
    int card_idx=0;
    for(int i=0;i<device_info.card_cnt;i++)
    {
      card_info_t* card_info = &device_info.card_info[i];
      if (card_info->card_type == DISCRETE_CARD_TYPE) {
        card_idx = i;
        break;
      }
    }
    printf("prepare_hwdec vendor_id             : %x\n", device_info.card_info[card_idx].vendor_id);
    setup_env(device_info.card_info[card_idx].vendor_id,hw_type);
    return 0;
}

static double calculte_usage(int userTicks, int sysTicks, double kPeriod, double kClockTicksPerSecond)
{
    return (userTicks + sysTicks) / (kClockTicksPerSecond * kPeriod);
}

static int read_procfile(int pid, long *out_utime, long *out_stime)
{
    FILE *fp;
    char filename[256];
    snprintf(filename, sizeof filename, "/proc/%d/stat", pid);

    int readpid;
    char name[64];
    char state;
    int ppid;
    int pgrp;
    int session;
    int tty_nr;
    int tpgid;
    int flags;
    long minflt;
    long cminflt;
    long majflt;
    long cmajflt;
    long utime;
    long stime;
    long cutime;
    long cstime;
    long priority;
    long nice;
    long num_threads;
    long itrealvalue;
    long starttime;

    fp = fopen(filename, "r");
    if (fp == NULL)
    {
        printf("fopen %s failed. file not exits\n", filename);
        return -1;
    }
    fscanf(fp, "%d %s %c %d %d %d %d %d %d %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld",
           &readpid,
           name,
           &state,
           &ppid,
           &pgrp,
           &session,
           &tty_nr,
           &tpgid,
           &flags,
           &minflt,
           &cminflt,
           &majflt,
           &cmajflt,
           &utime,
           &stime,
           &cutime,
           &cstime,
           &priority,
           &nice,
           &num_threads,
           &itrealvalue,
           &starttime);

    fclose(fp);

    *out_utime = utime;
    *out_stime = stime;

    return 0;
}

float get_cpu_usage(double time_period)
{
    int current_pid = getpid();
    int clockTicks = sysconf(_SC_CLK_TCK);
    float cpu_usage = 0.0;
    static long last_utime=0, last_stime=0;
    long utime=0, stime=0;
    read_procfile(current_pid, &utime, &stime);

    if (last_utime > 0 && last_stime > 0) {
      int userTicks = utime - last_utime;
      int sysTicks = stime - last_stime;
    //   printf("utime :%d, stime:%d period : %5.2f,clock ticks %d\n",utime,stime,time_period,clockTicks);
      cpu_usage =
          calculte_usage(userTicks, sysTicks, time_period, clockTicks) * 100;
    } else {
      last_utime = utime;
      last_stime = stime;
    }
    return cpu_usage;
}

void set_ffmpeg_log(int level)
{
  // TRACE(56),DEBUG(48),VERBOSE(40),INFO(32),WARNING(24),ERROR(16),default INFO
  int ffloglevel = level;
  char *str = getenv("FFMPEG_LOG_LEVEL");
  if (str) ffloglevel = atoi(str);
  av_log_set_level(ffloglevel);
}

static void print_lib_version(unsigned int version_num, const char *pname) {
  int version_major, version_minor, version_micro;
  version_major = (version_num >> 16) & 0xff;
  version_minor = (version_num >> 8) & 0xff;
  version_micro = (version_num)&0xff;
  printf("%-10s lib version: %d.%d.%d \n", pname, version_major, version_minor,
         version_micro);
}
static void print_headers_version(unsigned int version_num, const char *pname) {
  int version_major, version_minor, version_micro;

  version_major = (version_num >> 16) & 0xff;
  version_minor = (version_num >> 8) & 0xff;
  version_micro = (version_num)&0xff;
  printf("%-10s headers version: %d.%d.%d \n", pname, version_major,
         version_minor, version_micro);
}

typedef struct _FFVersion {
  unsigned int libavcodec_version;
  unsigned int libavformat_version;
  unsigned int libavdevice_version;
  unsigned int libavfilter_version;
  unsigned int libavutil_version;
  unsigned int libavresample_version;
  unsigned int libswrescale_version;
  unsigned int libpostproc_version;
} FFVersion;

typedef unsigned (*avapi_version_fuc)(void);

const char *psoavutil = "avutil";
const char *psoavcodec = "avcodec";
const char *psoavformat = "avformat";
const char *psoavdevice = "avdevice";

static unsigned get_lib_version(avapi_version_fuc av_version_func) {
  return av_version_func();
}
void get_ffmpeg_headers_version(FFVersion *version) {
  version->libavutil_version =
      AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR,
                     LIBAVUTIL_VERSION_MICRO);
  version->libavcodec_version =
      AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR,
                     LIBAVCODEC_VERSION_MICRO);
  version->libavformat_version =
      AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR,
                     LIBAVFORMAT_VERSION_MICRO);
}
void get_ffmpeg_lib_version(FFVersion *version) {
  version->libavutil_version = get_lib_version(avutil_version);
  version->libavcodec_version = get_lib_version(avcodec_version);
  version->libavformat_version = get_lib_version(avformat_version);
}
// static void print_ffmpeg_lib_version(FFVersion *version) {
//   print_lib_version(version->libavcodec_version, psoavcodec);
//   print_lib_version(version->libavutil_version, psoavutil);
//   print_lib_version(version->libavformat_version, psoavformat);
// }
// static void print_ffmpeg_headers_version(FFVersion *version) {
//   print_headers_version(version->libavcodec_version, psoavcodec);
//   print_headers_version(version->libavutil_version, psoavutil);
//   print_headers_version(version->libavformat_version, psoavformat);
// }
int get_ff_headers_version(int * libavcodec_ver,int * libavutil_ver,int * libavformat_ver)
{
  FFVersion ff_version={};
  get_ffmpeg_headers_version(&ff_version);
  *libavcodec_ver = ff_version.libavcodec_version;
  *libavutil_ver = ff_version.libavutil_version;
  *libavformat_ver = ff_version.libavformat_version;
  return 0;
}
int get_ff_lib_version(int * libavcodec_ver,int * libavutil_ver,int * libavformat_ver)
{
  FFVersion ff_version={};
  get_ffmpeg_lib_version(&ff_version);
  *libavcodec_ver = ff_version.libavcodec_version;
  *libavutil_ver = ff_version.libavutil_version;
  *libavformat_ver = ff_version.libavformat_version;
  return 0;
}
